/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package logger

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// Logger for user who want to customize logger
type Logger interface {
	Debug(args ...interface{})
	Info(args ...interface{})
	Warn(args ...interface{})
	Error(args ...interface{})
	Fatal(args ...interface{})

	Debugf(fmt string, args ...interface{})
	Infof(fmt string, args ...interface{})
	Warnf(fmt string, args ...interface{})
	Errorf(fmt string, args ...interface{})
	Fatalf(fmt string, args ...interface{})
}

// Config for configura Logger
type Config struct {
	LumberjackConfig *lumberjack.Logger `yaml:"lumberjack-config"`
	ZapConfig        *zap.Config        `yaml:"zap-config"`
	CallerSkip       int
}

// getEncoder get encoder by config, zapcore support json and console encoder
func (c *Config) getEncoder() zapcore.Encoder {
	if c.ZapConfig.Encoding == "json" {
		return zapcore.NewJSONEncoder(c.ZapConfig.EncoderConfig)
	} else if c.ZapConfig.Encoding == "console" {
		return zapcore.NewConsoleEncoder(c.ZapConfig.EncoderConfig)
	}
	return nil
}

// getLogWriter get Lumberjack writer by LumberjackConfig
func (c *Config) getLogWriter() zapcore.WriteSyncer {
	return zapcore.AddSync(c.LumberjackConfig)
}

// NewSmapleConfig new config
func NewSmapleConfig(level zapcore.Level) *Config {

	if level < zap.DebugLevel || level > zap.FatalLevel {
		level = zap.DebugLevel
	}

	zapLoggerEncoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "message",
		StacktraceKey:  "stacktrace",
		EncodeLevel:    zapcore.CapitalColorLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	zapConfig := &zap.Config{
		Level:            zap.NewAtomicLevelAt(level),
		Development:      false,
		Encoding:         "console",
		EncoderConfig:    zapLoggerEncoderConfig,
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}

	return &Config{ZapConfig: zapConfig}
}

// InnerLogger for logger instance
type InnerLogger struct {
	Logger
	Level zapcore.Level
}

func (log *InnerLogger) IsDebug() bool {
	return log.Level <= zap.DebugLevel
}

func (log *InnerLogger) IsInfo() bool {
	return log.Level <= zap.InfoLevel
}

func (log *InnerLogger) IsWarn() bool {
	return log.Level <= zap.WarnLevel
}

func (log *InnerLogger) IsError() bool {
	return log.Level <= zap.ErrorLevel
}

func (log *InnerLogger) IsFatal() bool {
	return log.Level <= zap.FatalLevel
}

// ///////////////////////////////////////
// init logger
// ///////////////////////////////////////

var (
	logger *InnerLogger
)

func InitLogger(conf *Config) {
	var zapLogger *zap.Logger

	if conf == nil {
		conf = NewSmapleConfig(zap.DebugLevel)
	}

	if conf.CallerSkip == 0 {
		conf.CallerSkip = 1
	}

	if conf.LumberjackConfig == nil {
		zapLogger, _ = conf.ZapConfig.Build(zap.AddCaller(), zap.AddCallerSkip(conf.CallerSkip))
	} else {
		zapLogger = initZapLoggerWithSyncer(conf)
	}

	logger = &InnerLogger{Logger: zapLogger.Sugar()}
}

// initZapLoggerWithSyncer init zap Logger with syncer
func initZapLoggerWithSyncer(conf *Config) *zap.Logger {
	core := zapcore.NewCore(
		conf.getEncoder(),
		conf.getLogWriter(),
		zap.NewAtomicLevelAt(conf.ZapConfig.Level.Level()),
	)
	return zap.New(core, zap.AddCaller(), zap.AddCallerSkip(conf.CallerSkip))
}

// GetLogger gets the logger
func GetLogger() *InnerLogger {
	return logger
}
